#include "dicom/put_handler.h"

namespace dicom {

static int SinkWrite(void* priv, const void* buf, size_t n) {
  PutSink* sink = static_cast<PutSink*>(priv);
  LOG_TRACE << "Write " << n << " Bytes";
  return sink->enc->Write(buf, n);
}

static int SinkClose(void* priv) {
  PutSink* sink = static_cast<PutSink*>(priv);
  int n = sink->enc->Finalize();
  sink->enc->Close();
  LOG_TRACE << "Close SINK: " << n;
  return n;
}

static const char* SinkName(void* priv) {
  PutSink* sink = static_cast<PutSink*>(priv);
  return sink->name.c_str();
}

static onion_put_sink_t* OnionNewPutSink(void* priv, size_t size) {
  PutSinkPool* pool = static_cast<PutSinkPool*>(priv);
  PutSink* s = pool->NewPutSink(size);
  if (s == NULL) {
    return NULL;
  }
  onion_put_sink_t* os = new (onion_put_sink_t);
  os->write = SinkWrite;
  os->close = SinkClose;
  os->name = SinkName;
  os->priv = s;
  return os;
}

static void OnionFreePutSink(void* priv, onion_put_sink_t* os) {
  PutSinkPool* pool = static_cast<PutSinkPool*>(priv);
  PutSink* sink = static_cast<PutSink*>(os->priv);
  pool->FreePutSink(sink);
  delete os;
}

PutSink* PutSinkPool::NewPutSink(size_t size) {
  std::string path;
  if (fcache_->AllocTmpFile("PUT", size, &path) == false) {
    return NULL;
  }

  FileWriter* w = new FileWriter();
  Status s = w->Open(path);
  if (!s.ok()) {
    fcache_->ReleaseTmpFile("PUT", path);
    delete w;
    return NULL;
  }

  PutSink* sink = new PutSink();
  sink->file = w;
  sink->enc = new EncryptStream(dicom::ByteArray::genRandom(32).toHexStr(), w);
  sink->name = path;
  sink->src_size = size;

  {
    LockGuard g(mu_);
    this->sinks_[sink->name] = sink;
  }

  return sink;
}

void PutSinkPool::FreePutSink(PutSink* s) {
  LockGuard g(mu_);
  LOG_TRACE << "erase sink: " << s->name;
  sinks_.erase(s->name);
  fcache_->ReleaseTmpFile("PUT", s->name);
  delete s;
}

PutSink* PutSinkPool::FindSink(const std::string& path) {
  LockGuard g(mu_);
  if (sinks_.find(path) != sinks_.end()) {
    return sinks_[path];
  }
  return NULL;
}

onion_put_sink_pool_t* PutSinkPool::OnionPool() {
  onion_put_sink_pool_t* p = new (onion_put_sink_pool_t);
  p->alloc_sink = OnionNewPutSink;
  p->free_sink = OnionFreePutSink;
  p->priv = this;
  return p;
}
}
